Shader "FullScreen/VolumeFog"
{
    Properties
    {
        _FogMax ("FogMax", Float) = 20
        _FogDensity ("FogDensity", Float) = 0.01
        _Loop ("Loop", Float) = 32

        _ZNear ("ZNear", Float) = 0.1
        _ZFar ("ZFar", Float) = 5
        _HBottom ("HBottom", Float) = 0
        _HTop ("HTop", Float) = 5
        _MarchingNear ("MarchingNear", Float) = 1
        _MarchingStep ("MarchingStep", Float) = 0.2
        _MarchMaxDistance ("MarchMaxDistance", Float) = 10
        _g ("G", Vector) = (1, 1, 1, 1)
        _mieG ("_mieG", Vector) = (1, 1, 1, 1)
    }

    HLSLINCLUDE

    #pragma vertex Vert

    #pragma target 4.5
    #pragma only_renderers d3d11 playstation xboxone xboxseries vulkan metal switch
    #pragma multi_compile SHADOW_ULTRA_LOW
    
    // #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/CustomPass/CustomPassCommon.hlsl"

    #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
    #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
    #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl"
    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/CustomPass/CustomPassInjectionPoint.cs.hlsl"
    
    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/NormalBuffer.hlsl"
    
    #define LIGHTLOOP_DISABLE_TILE_AND_CLUSTER
    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/PunctualLightCommon.hlsl"
    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/HDShadow.hlsl"

    #define random(seed) sin(seed * 641.5467987313875 + 1.943856175)

    float _FogMax;
    float _FogDensity;

    float _ZNear;
    float _ZFar;
    float _HBottom;
    float _HTop;
    float _MarchingNear;
    float _MarchingStep;
    float _MarchMaxDistance;
    float _Loop;
    float4 _g;
    float4 _mieG;

    float _CustomPassInjectionPoint;
    float _FadeValue;

    TEXTURE2D_X(_Buffer);

    // This texture is only available in after post process and contains the result of post processing effects.
    // While SampleCameraColor still returns the color pyramid without post processes
    TEXTURE2D_X(_AfterPostProcessColorBuffer);

    float3 CustomPassSampleCameraColor(float2 uv, float lod, bool uvGuards = true)
    {
        if (uvGuards)
            uv = clamp(uv, 0, 1 - _ScreenSize.zw / 2.0);

        switch ((int)_CustomPassInjectionPoint)
        {
            case CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING: return float3(0, 0, 0);
            // there is no color pyramid yet for before transparent so we can't sample with mips.
            // Also, we don't use _RTHandleScaleHistory to sample because the color pyramid bound is the actual camera color buffer which is at the resolution of the camera
            case CUSTOMPASSINJECTIONPOINT_BEFORE_TRANSPARENT:
            case CUSTOMPASSINJECTIONPOINT_BEFORE_PRE_REFRACTION: return SAMPLE_TEXTURE2D_X_LOD(_ColorPyramidTexture, s_trilinear_clamp_sampler, uv * _RTHandleScaleHistory.xy, 0).rgb;
            case CUSTOMPASSINJECTIONPOINT_AFTER_POST_PROCESS: return SAMPLE_TEXTURE2D_X_LOD(_AfterPostProcessColorBuffer, s_trilinear_clamp_sampler, uv * _RTHandleScaleHistory.xy, 0).rgb;
            default: return SampleCameraColor(uv, lod);
        }
    }

    float3 CustomPassLoadCameraColor(uint2 pixelCoords, float lod)
    {
        switch ((int)_CustomPassInjectionPoint)
        {
            case CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING: return float3(0, 0, 0);
            // there is no color pyramid yet for before transparent so we can't sample with mips.
            case CUSTOMPASSINJECTIONPOINT_BEFORE_TRANSPARENT:
            case CUSTOMPASSINJECTIONPOINT_BEFORE_PRE_REFRACTION: return LOAD_TEXTURE2D_X_LOD(_ColorPyramidTexture, pixelCoords, 0).rgb;
            case CUSTOMPASSINJECTIONPOINT_AFTER_POST_PROCESS: return LOAD_TEXTURE2D_X_LOD(_AfterPostProcessColorBuffer, pixelCoords, 0).rgb;
            default: return LoadCameraColor(pixelCoords, lod);
        }
    }

    struct Attributes
    {
        uint vertexID : SV_VertexID;
        UNITY_VERTEX_INPUT_INSTANCE_ID
    };

    struct Varyings
    {
        float4 positionCS : SV_POSITION;
        UNITY_VERTEX_OUTPUT_STEREO
    };

    Varyings Vert(Attributes input)
    {
        Varyings output;
        UNITY_SETUP_INSTANCE_ID(input);
        UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
        // output.positionCS = GetFullScreenTriangleVertexPosition(input.vertexID, UNITY_RAW_FAR_CLIP_VALUE);
        float2 uv = float2((input.vertexID << 1) & 2, input.vertexID & 2);
        output.positionCS =  float4(uv * 2.0 - 1.0, UNITY_RAW_FAR_CLIP_VALUE, 1.0);
        return output;
    }

    // The PositionInputs struct allow you to retrieve a lot of useful information for your fullScreenShader:
    // struct PositionInputs
    // {
    //     float3 positionWS;  // World space position (could be camera-relative)
    //     float2 positionNDC; // Normalized screen coordinates within the viewport    : [0, 1) (with the half-pixel offset)
    //     uint2  positionSS;  // Screen space pixel coordinates                       : [0, NumPixels)
    //     uint2  tileCoord;   // Screen tile coordinates                              : [0, NumTiles)
    //     float  deviceDepth; // Depth from the depth buffer                          : [0, 1] (typically reversed)
    //     float  linearDepth; // View space Z coordinate                              : [Near, Far]
    // };

    // To sample custom buffers, you have access to these functions:
    // But be careful, on most platforms you can't sample to the bound color buffer. It means that you
    // can't use the SampleCustomColor when the pass color buffer is set to custom (and same for camera the buffer).
    // float4 SampleCustomColor(float2 uv);
    // float4 LoadCustomColor(uint2 pixelCoords);
    // float LoadCustomDepth(uint2 pixelCoords);
    // float SampleCustomDepth(float2 uv);

    // There are also a lot of utility function you can use inside Common.hlsl and Color.hlsl,
    // you can check them out in the source code of the core SRP package.
    // #define SHADOW_AUTO_FLIP_NORMAL  

    static float3x3 steps[] = 
    {
        {
            2, 9, 4,  
            7, 5, 3, 
            6, 1, 8
        },
        {
            7, 2, 9,  
            6, 5, 4, 
            1, 8, 3
        },
        {
            6, 7, 2,  
            1, 5, 9, 
            8, 3, 4
        },
        {
            1, 6, 7,  
            8, 5, 2, 
            3, 4, 9
        },
        {
            8, 1, 6,  
            3, 5, 7, 
            4, 9, 2
        },
        {
            3, 8, 1,  
            4, 5, 6, 
            9, 2, 7
        },
        {
            4, 3, 8,  
            9, 5, 1, 
            2, 7, 6
        },
        {
            9, 4, 3,  
            2, 5, 8, 
            7, 6, 1
        },
    };

    #define MieScattering(cosAngle, g) g.w * (g.x / (pow(g.y - g.z * cosAngle, 1.5)))

    float4 FullScreenPass(Varyings varyings) : SV_Target
    {
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(varyings);
        float depth = LoadCameraDepth(varyings.positionCS.xy);
        PositionInputs posInput = GetPositionInput(varyings.positionCS.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V);
        float3 viewDirection = GetWorldSpaceNormalizeViewDir(posInput.positionWS);
        float4 color = float4(0.0, 0.0, 0.0, 0.0);

        // Load the camera color buffer at the mip 0 if we're not at the before rendering injection point
        if (_CustomPassInjectionPoint != CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING)
            color = float4(CustomPassLoadCameraColor(varyings.positionCS.xy, 0), 1);

        // Add your custom pass code here
        // 光线步进
        float3 origin = _WorldSpaceCameraPos;
        float3 direction = posInput.positionWS;
        float len = min(length(direction), _FogMax);
        float extra = length(direction) - len;
        len -= 0.01;

        direction = normalize(direction) * len;
        float maxLen = length(direction) - 0.01; 

        int loopCount = _Loop; 
        float loopCountInv = 1.0 / loopCount;

        HDShadowContext shadowContext = InitShadowContext();
        DirectionalLightData light = _DirectionalLightDatas[_DirectionalShadowIndex];
        float3 L = -light.forward;

        int i, j, k = 1;
        float ji = 1;

        float t = _Time.x + 1;
        float seed1 = random(posInput.positionNDC.x * 10 * (t % 10.0));
        float seed2 = random(posInput.positionNDC.y * 10 * (t % 10.0));
        float seed3 = random(t);
        
        float seed = random(abs(seed1) * 1000 + abs(seed2) * 100 + abs(seed3) * 10);
        seed = random(abs(seed));

        
        float step = (steps[int((_Time.w * 10) % 8)][(posInput.positionSS.x) % 3.0][(posInput.positionSS.y) % 3.0] - 1) / 9.0; 
        // step += _Time.z;
        // step = step % 1.0;

        // return float4(step, step, step, 1);
        float step1 = steps[(posInput.positionSS.x + seed) % 3.0][(posInput.positionSS.y + seed) % 3.0] / 9.0; 
        
        // 8 -> 6 ~ 10
        // loopCount = loopCount / 4.0 * 3.0 + loopCount / 2.0 * ();
        // loopCountInv = 1.0 / loopCount;
        // float nLoopCountInv = loopCountInv / 9.0;
        
        // float base = 0.01 * abs(seed);

        float shadowC = 0;
        for (i = 0; i < loopCount; i++)
        {
            seed = abs(random(seed));
            float len = seed;
            len = ((i + step + seed / 9.0) * loopCountInv);
            // len = step + (i + seed) * loopCountInv / 9.0;

            float3 currentPoint = direction * len;
            float shadow = GetDirectionalShadowAttenuation(shadowContext,
                                    posInput.positionSS, currentPoint, float3(0, 0, 0),
                                    light.shadowIndex, L);

            float fogHightDensity = ((_WorldSpaceCameraPos + currentPoint).y - _HTop) / (_HBottom - _HTop);
            fogHightDensity = saturate(fogHightDensity);

            shadowC += (shadow + shadow * fogHightDensity) * loopCountInv; 
            // shadowC += shadow * loopCountInv; 
        }

        float cosAngle = dot(L, direction);
        float fogDensity = (shadowC * maxLen) * _FogDensity * saturate(MieScattering(cosAngle, _mieG));
        
        float a = saturate(fogDensity);
        // color.rg = (1 - a) * color.rgb + a * fogDensity * GetCurrentExposureMultiplier() * light.color;
        color.rg = fogDensity * GetCurrentExposureMultiplier() * light.color;
        color.b = depth;
        color.a = a;

        // Fade value allow you to increase the strength of the effect while the camera gets closer to the custom pass volume
        float f = 1 - abs(_FadeValue * 2 - 1);
 
        return color;
    } 

    static float2 samplingPositions[8] =
    {
        float2( 0,  1),
        float2(-1,  0),
        float2( 0, -1),
        float2( 1, 0),
        float2( 1,  1),
        float2(-1,  1),
        float2(-1, -1),
        float2( 1, -1),
    };

    float4 Denoise(Varyings varyings) : SV_Target
    {
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(varyings);
        float depth = LoadCameraDepth(varyings.positionCS.xy);
        PositionInputs posInput = GetPositionInput(varyings.positionCS.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V);
        float3 viewDirection = GetWorldSpaceNormalizeViewDir(posInput.positionWS);
        float4 color = float4(0.0, 0.0, 0.0, 0.0);

        // Load the camera color buffer at the mip 0 if we're not at the before rendering injection point
        if (_CustomPassInjectionPoint != CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING)
            color = float4(CustomPassLoadCameraColor(varyings.positionCS.xy, 0), 1);

        float2 uv = posInput.positionNDC.xy * _RTHandleScale.xy;
        color = SAMPLE_TEXTURE2D_X_LOD(_Buffer, s_point_clamp_sampler, uv, 0);
        color.a = 1;

        float4 k1 = 0, k2 = 0;
        int i = 0;
        UNITY_LOOP
        for(i = 0; i < 4; i++)
        {
            float2 uvN = uv + _ScreenSize.zw * _RTHandleScale.xy * samplingPositions[i];
            float4 n = SAMPLE_TEXTURE2D_X_LOD(_Buffer, s_point_clamp_sampler, uvN, 0);

            k1 += n;
        }
        k1 /= 4.0;
        k1.a = 1;

        if (abs(k1.r - color.r) >= _g.x)
        {
            return k1;
        }

        UNITY_LOOP
        for(i = 4; i < 8; i++)
        {
            float2 uvN = uv + _ScreenSize.zw * _RTHandleScale.xy * samplingPositions[i];
            float4 n = SAMPLE_TEXTURE2D_X_LOD(_Buffer, s_point_clamp_sampler, uvN, 0);

            k2 += n;
        }
        k2 /= 4.0;
        k2.a = 1;

        if (abs(k2.r - color.r) >= _g.x)
        {
            return k2;
        }

        return color;
    }

    float4 Blur(Varyings varyings) : SV_Target
    {
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(varyings);
        float depth = LoadCameraDepth(varyings.positionCS.xy);
        PositionInputs posInput = GetPositionInput(varyings.positionCS.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V);
        float3 viewDirection = GetWorldSpaceNormalizeViewDir(posInput.positionWS);
        float4 color = float4(0.0, 0.0, 0.0, 0.0);
        float4 realColor = float4(0.0, 0.0, 0.0, 0.0);

        // Load the camera color buffer at the mip 0 if we're not at the before rendering injection point
        if (_CustomPassInjectionPoint != CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING)
            realColor = float4(CustomPassLoadCameraColor(varyings.positionCS.xy, 0), 1);

        float2 uv = posInput.positionNDC.xy * _RTHandleScale.xy;
        color = SAMPLE_TEXTURE2D_X_LOD(_Buffer, s_point_clamp_sampler, uv, 0);
        color.a = 1;

        float4 k1 = 0;
        float a = 1, count = 1;
        int i = 0;
        UNITY_LOOP
        for(i = 0; i < 8; i++)
        {
            float2 uvN = uv + _ScreenSize.zw * _RTHandleScale.xy * samplingPositions[i];
            float4 n = SAMPLE_TEXTURE2D_X_LOD(_Buffer, s_point_clamp_sampler, uvN, 0);

            if (n.b - color.b < _g.z)
            {
                k1 += n;
                a *= n.g;
                count++;
            }
        }

        DirectionalLightData light = _DirectionalLightDatas[_DirectionalShadowIndex];
        float3 L = -light.forward;
        float3 direction = posInput.positionWS;
        
        k1 += color;
        a *= color.g;
        k1 /= count;
        k1.a = saturate(k1.a);

        k1.b = k1.r;

        return float4(lerp(realColor.rgb, k1.rgb, k1.a), 1);
    }

    float4 ToScreen(Varyings varyings) : SV_Target
    {
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(varyings);
        float depth = LoadCameraDepth(varyings.positionCS.xy);
        PositionInputs posInput = GetPositionInput(varyings.positionCS.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V);
        float3 viewDirection = GetWorldSpaceNormalizeViewDir(posInput.positionWS);
        float4 color = float4(0.0, 0.0, 0.0, 0.0);

        // Load the camera color buffer at the mip 0 if we're not at the before rendering injection point
        if (_CustomPassInjectionPoint != CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING)
            color = float4(CustomPassLoadCameraColor(varyings.positionCS.xy, 0), 1);
        
        float2 uv = posInput.positionNDC.xy * _RTHandleScale.xy;
        color = SAMPLE_TEXTURE2D_X_LOD(_Buffer, s_point_clamp_sampler, uv, 0);
        color.a = 1;
        color.gb = color.r;

        return color;
    }

    ENDHLSL

    SubShader
    {
        Tags{ "RenderPipeline" = "HDRenderPipeline" }
        Pass
        {
            Name "Custom Pass 0"

            ZWrite Off
            ZTest Always
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off

            HLSLPROGRAM
                #pragma fragment FullScreenPass
            ENDHLSL
        }

        Pass
        {
            Name "Denoise"

            ZWrite Off
            ZTest Always
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off

            HLSLPROGRAM
                #pragma fragment Denoise
            ENDHLSL
        }

        Pass
        {
            Name "ToScreen"

            ZWrite Off
            ZTest Always
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off

            HLSLPROGRAM
                #pragma fragment ToScreen
            ENDHLSL
        }

        Pass
        {
            Name "ToScreen"

            ZWrite Off
            ZTest Always
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off

            HLSLPROGRAM
                #pragma fragment Blur
            ENDHLSL
        }
    }
    Fallback Off
}
